package com.retrofit.server.processor;

import com.retrofit.server.processor.entity.ClassEntity;
import com.retrofit.server.processor.entity.GenericEntity;
import com.retrofit.server.processor.entity.MethodEntity;
import com.retrofit.server.processor.entity.MethodReturnEntity;
import com.retrofit.server.processor.entity.ParameterEntity;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;

/**
 * @author GYL
 * @dateTime 2020/5/9-16:30
 * @description
 */
public class JClassGenerate {

  private static final String SUFFIX = "Service";

  private String pkgName;
  private TypeSpec.Builder mTypeSpceBuilder;
  private MethodSpec.Builder mMethodSpceBuilder;

  public JClassGenerate generateClass(ClassEntity classEntity) {
    if (mTypeSpceBuilder == null) {
      pkgName = classEntity.getPackageName();
      CodeBlock.Builder javaDocBuilder = CodeBlock.builder();
      javaDocBuilder.add(classEntity.getJavadoc());
      mTypeSpceBuilder = TypeSpec.classBuilder(classEntity.getClazzName() + SUFFIX)
          .addModifiers(Modifier.PUBLIC)
          //生成内部service 变量
          .addField(generateFiled(classEntity.getPackageName(), classEntity.getClazzName()))
          //生成What 计数器
          .addField(generateCountWhatFiled())
          //生成内部Service实例
          .addMethod(generateMethod(classEntity.getPackageName(), classEntity.getClazzName()))
                           .addMethod(generateRemoveMethod())
          .addMethod(generateCountWhatMethod())
           .addMethod(generateEnqueueMethod(classEntity.getApiGenerateService(),classEntity.isUseHandlerMehthod()))
          .addJavadoc(javaDocBuilder.build());

      //添加接口方法
      //List<AnnotationClassMethod> methodList = annotationClass.getMethodList();
      List<MethodEntity> methodList = classEntity.getMethodEntityList();
      if (methodList != null) {
        for (MethodEntity methodEntity : methodList) {
          MethodSpec methodSpec = generateMethodImpl(methodEntity);
          mTypeSpceBuilder.addMethod(methodSpec);
        }
      }
    }
    return this;
  }

  /**
   * 生成Java 类文件
   */
  public void generateJavaFile(ProcessingEnvironment processingEnv) {
    if (mTypeSpceBuilder != null) {
      JavaFile javaFile = JavaFile.builder(pkgName, mTypeSpceBuilder.build())
          .skipJavaLangImports(true)
          .addFileComment("Retrofit Api Interface implement class,auto generation")
          .build();
      try {
        //javaFile.writeTo(System.out);
        javaFile.writeTo(processingEnv.getFiler());
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  public static void checkValidClass() throws ProcessorException {

  }

  /**
   * 生成公共的 _innerCall 属性
   *
   * @param pkgName 接口服务包名
   * @param clzzName 接口服务类名
   */
  private FieldSpec generateFiled(String pkgName, String clzzName) {
    ClassName elClazz = ClassName.get(pkgName, clzzName);
    FieldSpec.Builder fb = FieldSpec
        .builder(elClazz, "_innerCall", Modifier.PRIVATE, Modifier.STATIC);
    return fb.build();
  }

  /**
   * 创建公共请求类 init 方法
   */
  private MethodSpec generateMethod(String pkgName, String clzzName) {
    ClassName elClazz = ClassName.get(pkgName, clzzName);
    MethodSpec.Builder mb = MethodSpec.methodBuilder("innerCall")
        .addModifiers(Modifier.STATIC, Modifier.PRIVATE)
        .addJavadoc("创建Api服务\n")
        .returns(elClazz)
        .addCode("if(_innerCall==null){\n"
            + "$W$W_innerCall=com.okhttp.retrofit.RetrofitClient.getInstance().createService($T.class);"
            + "\n}\n"
            + "return _innerCall;\n", elClazz);
    return mb.build();
  }
  
  private MethodSpec generateRemoveMethod(){
    MethodSpec.Builder mb = MethodSpec.methodBuilder("removeSelf")
                              .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                              .addJavadoc("清除服务\n")
                              .addCode("sNextGeneratedId=null;\n")
                              .addCode("_innerCall=null;\n"
                                         + "\n");
    return mb.build();
  }

  /**
   * 生成 what计数器
   */
  private FieldSpec generateCountWhatFiled() {
    //static final AtomicInteger sNextGeneratedId = new AtomicInteger(100);
    ClassName className = ClassName.get("java.util.concurrent.atomic", "AtomicInteger");
    FieldSpec.Builder fb = FieldSpec.builder(className, "sNextGeneratedId")
        .addModifiers(Modifier.PRIVATE, Modifier.STATIC)
        //.initializer("new AtomicInteger(10)")
        .addJavadoc("生成接口唯一码（WHAT）\n");
    return fb.build();
  }

  /**
   * 生成What
   */
  private MethodSpec generateCountWhatMethod() {
    //  private static int generateWhat() {
    //    for (;;) {
    //      final int result = sNextGeneratedId.get();
    //      // aapt-generated IDs have the high byte nonzero; clamp to the range
    //      // under that.
    //      int newValue = result + 1;
    //      if (newValue > 0x00FFFFFF) {
    //        // Roll over to 1, not 0.
    //        newValue = 1;
    //      }
    //      if (sNextGeneratedId.compareAndSet(result, newValue)) {
    //        return result;
    //      }
    //    }
    //  }
    ClassName className = ClassName.get("java.util.concurrent.atomic", "AtomicInteger");
    MethodSpec.Builder mb = MethodSpec.methodBuilder("generateWhat")
        .addModifiers(Modifier.STATIC, Modifier.PRIVATE)
        .addJavadoc("生成接口唯一码(What)\n")
        .returns(TypeName.INT)
        .addCode("if(sNextGeneratedId==null){\n sNextGeneratedId= new AtomicInteger(1); \n}\n")
        .addCode("for (;;) {\n")
        .addCode("$W final int result = sNextGeneratedId.get();\n")
        .addCode(
            "$W$W // aapt-generated IDs have the high byte nonzero; clamp to the range under that.\n")
        .addCode("$W$W$W int newValue = result + 1;\n")
        .addCode("$W$W$W if (newValue > 0x00FFFFFF) {\n")
        .addCode("$W$W$W // Roll over to 1, not 0.\n")
        .addCode("$W$W$W newValue = 1;\n}\n")
        .addCode("$W if (sNextGeneratedId.compareAndSet(result, newValue)) {\n")
        .addCode("$W return result;$W \n}\n}");
    return mb.build();

  }
  
  private MethodSpec generateEnqueueMethod(TypeMirror apiGenerateService,boolean isUseHandlerMethod){
    /*
    private static int enqueue(String methodName,ResultHandler handler,Object... params) {
      int what=generateWhat()+handler.hashCode();
      handler.onBeforeExecute(what);
      try {
           Class[] paramClz=null;
      if(params!=null){
        paramClz=new Class[params.length];
        for (int i = 0; i < params.length; i++) {
          paramClz[i]=params[i].getClass();
        }
      }
        Method method = innerCall().getClass().getMethod(methodName, paramClz);
        Call call = (Call) method.invoke(_innerCall, params);
        Method method1 = call.getClass().getMethod("enqueue", Callback.class);
        method1.invoke(call, handler);
      } catch (Exception e) {
        e.printStackTrace();
        LogUtil.e(e.getMessage(),e);
      }
      return what;
    }
    */
    MethodSpec.Builder mb = MethodSpec.methodBuilder("enqueue")
                              .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
                              .addJavadoc("通用异步执行请求方法\n")
                              .addJavadoc("@param methodName 方法名称\n")
                              .addJavadoc("@param handler 回调方法\n")
                              .addJavadoc("@param params 参数列表\n")
                              .returns(TypeName.INT)
                              .addParameter(String.class, "methodName")
                              .addParameter(TypeName.get(apiGenerateService), "handler")
                              .addParameter(Object[].class, "params")
                              .varargs(true)
                              .addCode("int what=generateWhat()+handler.hashCode();\n")
                              .addCode(isUseHandlerMethod?"handler.onBeforeExecute(what);\n":"")
                              .addCode("try {\n")
                              .addCode("Class[] paramClz=null;\n")
                              .addCode("if(params!=null){\n")
                              .addCode("paramClz=new Class[params.length];\n")
                              .addCode("for (int i = 0; i < params.length; i++) {\n")
                              .addCode("paramClz[i]=params[i].getClass();\n}\n}\n")
                              .addCode("java.lang.reflect.Method method = innerCall().getClass().getMethod(methodName, paramClz);\n")
                              .addCode("retrofit2.Call call = (retrofit2.Call) method.invoke(_innerCall, params);\n")
                              .addCode("com.okhttp.retrofit.RetrofitClient.getInstance().addRetrofitCall(what,call);\n")
                              .addCode("java.lang.reflect.Method method1 = call.getClass().getMethod(\"enqueue\", retrofit2.Callback.class);\n")
                              .addCode("method1.invoke(call, handler);\n")
                              .addCode("} catch (Exception e) {\n")
                              .addCode("e.printStackTrace();\n")
                              .addCode("}\n")
                              .addCode("return what;\n");
      
    return mb.build();
  }
  

  /**
   * 实现接口服务中的方法
   */
  public MethodSpec generateMethodImpl(MethodEntity classMethod) {
    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(classMethod.getMethodName())
        .addModifiers(Modifier.STATIC, Modifier.PUBLIC)
        .addJavadoc(classMethod.getJavaDoc());
    //添加参数
    if (classMethod.getParameterEntityList() != null) {
      for (ParameterEntity methodParam : classMethod.getParameterEntityList()) {
        Print.log(methodParam.getClassTypeName().toString());
        if(methodParam.isHandler()) {
          ClassName className = (ClassName) ClassName.get(methodParam.getClassTypeName());
          GenericEntity genericEntity = methodParam.getGenericEntities();
          if (genericEntity != null && !StringUtils.isListNull(genericEntity.getTypeNameList())) {
            TypeName[] typeNames = new TypeName[genericEntity.getTypeNameList().size()];
            ParameterizedTypeName pTypeName = ParameterizedTypeName
                .get(className, genericEntity.getTypeNameList().toArray(typeNames));
            ParameterSpec parameterSpec = ParameterSpec
                .builder(pTypeName, methodParam.getParamName())
                .build();
            methodBuilder.addParameter(parameterSpec);
            continue;
          }
        }
        TypeName typeName = TypeName.get(methodParam.getClassTypeName());
        methodBuilder.addParameter(typeName, methodParam.getParamName());
      }
    }

    //添加代码块
    if (classMethod.getMethodBodyEntity() != null) {
      methodBuilder.addCode(classMethod.getMethodBodyEntity().getCodeBlock());
    }
    //fixme
    MethodReturnEntity methodReturnEntity=classMethod.getReturnEntity();
    if(methodReturnEntity!=null) {
      TypeMirror returnTypeMirror = methodReturnEntity.getTmMethodReturn();
      if (returnTypeMirror != null) {
        //反回Int
        if("java.lang.Integer".equals(returnTypeMirror.toString())){
          methodBuilder.returns(TypeName.get(int.class));
          if (methodReturnEntity.isGenericT()) {
            methodBuilder.addTypeVariable(TypeVariableName.get("T"));
          }
        }else{
          if(methodReturnEntity.isSync()){
            //同步方法返回
            if(methodReturnEntity.getGenericEntity()!=null
                && !StringUtils.isListNull(methodReturnEntity.getGenericEntity().getTypeNameList())){
              TypeName[] typeNames=new TypeName[methodReturnEntity.getGenericEntity().getTypeNameList().size()];
              methodReturnEntity.getGenericEntity().getTypeNameList().toArray(typeNames);
              ParameterizedTypeName parameterizedTypeName;
              TypeName typeName = TypeName.get(returnTypeMirror);
              if(typeName instanceof ClassName){
                ClassName className= (ClassName) TypeName.get(returnTypeMirror);
                parameterizedTypeName=ParameterizedTypeName.get(className,typeNames);
                methodBuilder.returns(parameterizedTypeName);
              }else{
                TypeElement element = (TypeElement) ((DeclaredType) returnTypeMirror).asElement();
                ClassName className=ClassName.get(element);
                parameterizedTypeName=ParameterizedTypeName.get(className,typeNames);
                methodBuilder.returns(parameterizedTypeName);
              }
            }else {
              methodBuilder.addTypeVariable(TypeVariableName.get("T"));
              methodBuilder.returns(TypeVariableName.get("T"));
              //methodBuilder.returns(TypeName.get(returnTypeMirror));
            }
          }else{
            //其他方法：
            methodBuilder.returns(TypeName.get(returnTypeMirror));
          }
        }
      }
    }
    return methodBuilder.build();
  }

  /**
   * 截取包名和类名
   */
  public String[] splitPackage$Class(String type) {
    String clazzName = type.substring(type.lastIndexOf(".") + 1, type.length());
    String packageName = type.substring(0, type.lastIndexOf('.'));
    return new String[]{packageName, clazzName};
  }

  /**
   * 获取注解的值 ，可为空
   *
   * @param annotationName 注解的类名
   * @param filter 值的key
   */
  protected AnnotationValue getAnnotationValue(Element el, String annotationName, String filter) {
    //获取方法的Annotations
    List<? extends AnnotationMirror> ams = el.getAnnotationMirrors();
    for (AnnotationMirror am : ams) {
      log("annotationMirror:" + am.toString());
      log("annotationMirror:" + am.getAnnotationType());
      if (am.getAnnotationType().toString().endsWith(annotationName)) {
        Map<? extends ExecutableElement, ? extends AnnotationValue> map = am.getElementValues();
        Set<? extends ExecutableElement> setEl = map.keySet();
        for (ExecutableElement exel : setEl) {
          //获取Annotation的值
          if (exel.getSimpleName().toString().equals(filter)) {
            AnnotationValue anvalue = map.get(exel);
            return anvalue;
          }
        }
      }
    }
    return null;
  }

  protected void log(Object msg) {
    Print.log(String.format("%: >>> %s", getClass().getName(), String.valueOf(msg)));
  }

}
